﻿namespace TrinityEngine
{
    public struct Vector3 : IEquatable<Vector3>
    {
        public float x;

        public float y;

        public float z;

        public float sqrMagnitude => x * x + y * y + z * z;

        public float magnitude => Mathf.Sqrt(sqrMagnitude);

        public Vector3 normalized
        {
            get
            {
                if (x == 0.0f && y == 0.0f && z == 0.0f)
                {
                    return zero;
                }
                else
                {
                    return new Vector3(x / magnitude, y / magnitude, z / magnitude);
                }
            }
        }

        public Vector3(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public readonly static Vector3 zero = new Vector3(0.0f, 0.0f, 0.0f);

        public readonly static Vector3 one = new Vector3(1.0f, 1.0f, 1.0f);

        public readonly static Vector3 up = new Vector3(0.0f, 1.0f, 0.0f);

        public readonly static Vector3 down = new Vector3(0.0f, -1.0f, 0.0f);

        public readonly static Vector3 left = new Vector3(-1.0f, 0.0f, 0.0f);

        public readonly static Vector3 right = new Vector3(1.0f, 0.0f, 0.0f);

        public readonly static Vector3 foward = new Vector3(0.0f, 0.0f, 1.0f);

        public readonly static Vector3 back = new Vector3(.0f, 0.0f, -1.0f);

        public Vector2 ToVector2()
        {
            return new Vector2(x, y);
        }

        public static Vector3 operator -(Vector3 a)
        {
            return new Vector3(-a.x, -a.y, -a.z);
        }

        public static Vector3 operator +(Vector3 a, Vector3 b)
        {
            return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
        }

        public static Vector3 operator -(Vector3 a, Vector3 b)
        {
            return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
        }

        public static Vector3 operator *(Vector3 a, Vector3 b)
        {
            return new Vector3(a.x * b.x, a.y * b.y, a.z * b.z);
        }

        public static Vector3 operator *(Vector3 a, float b)
        {
            return new Vector3(a.x * b, a.y * b, a.z * b);
        }

        public static Vector3 operator *(float a, Vector3 b)
        {
            return b * a;
        }

        public static Vector3 operator *(Vector3 a, Vector3Int b)
        {
            return new Vector3(a.x * b.x, a.y * b.y, a.z * b.z);
        }

        public static Vector3 operator *(Vector3Int a, Vector3 b)
        {
            return b * a;
        }

        public static Vector3 operator /(Vector3 a, Vector3 b)
        {
            if (b.x * b.y * b.z == 0.0f)
            {
                throw new Exception("Dividend could not be zero!");
            }

            return new Vector3(a.x / b.x, a.y / b.y, a.z / b.z);
        }

        public static Vector3 operator /(Vector3 a, float b)
        {
            if (b == 0.0f)
            {
                throw new Exception("Dividend could not be zero!");
            }

            return new Vector3(a.x / b, a.y / b, a.z / b);
        }

        public static Vector3 operator /(float a, Vector3 b)
        {
            return b / a;
        }

        public bool Equals(Vector3 other)
        {
            return x == other.x && y == other.y && z == other.z;
        }

        public override bool Equals(object obj)
        {
            if (obj is Vector3 v)
            {
                return Equals(v);
            }

            return false;
        }

        public override int GetHashCode()
        {
            return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode();
        }

        public override string ToString()
        {
            return $"Vector3({x}, {y}, {z})";
        }

        public static bool operator ==(Vector3 a, Vector3 b)
        {
            return a.Equals(b);
        }

        public static bool operator !=(Vector3 a, Vector3 b)
        {
            return !a.Equals(b);
        }

        public static explicit operator Vector3Int(Vector3 a)
        {
            return new Vector3Int((int)a.x, (int)a.y, (int)a.z);
        }

        public static implicit operator Vector3(System.Numerics.Vector3 a)
        {
            return new Vector3(a.X, a.Y, a.Z);
        }

        public static implicit operator System.Numerics.Vector3(Vector3 a)
        {
            return new System.Numerics.Vector3(a.x, a.y, a.z);
        }

        public static implicit operator Vector3(OpenTK.Mathematics.Vector3 a)
        {
            return new Vector3(a.X, a.Y, a.Z);
        }

        public static implicit operator OpenTK.Mathematics.Vector3(Vector3 a)
        {
            return new OpenTK.Mathematics.Vector3(a.x, a.y, a.z);
        }

        public Vector3Int RoundToInt()
        {
            return new Vector3Int(Mathf.RoundToInt(x), Mathf.RoundToInt(y), Mathf.RoundToInt(z));
        }

        public Vector3Int FloorToInt()
        {
            return new Vector3Int(Mathf.FloorToInt(x), Mathf.FloorToInt(y), Mathf.FloorToInt(z));
        }

        public Vector3Int CeilToInt()
        {
            return new Vector3Int(Mathf.CeilToInt(x), Mathf.RoundToInt(y), Mathf.CeilToInt(z));
        }
    }
}
